package valuecomusa.fijibus.eticket.service.shorthaul;

import android.app.ActivityManager;
import android.app.Service;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.*;
import android.util.Log;
import android.widget.Toast;
import valuecomusa.fijibus.eticket.db.shorthaul.BusinessDBHandler;
import valuecomusa.fijibus.eticket.db.shorthaul.GpsDBHandler;
import valuecomusa.fijibus.eticket.dto.GpsCurrentLocationValue;
import valuecomusa.fijibus.eticket.dto.StagesOfRouteValue;
import valuecomusa.fijibus.eticket.ws.JournalValue;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Created with IntelliJ IDEA.
 * User: fireman
 * Date: 5/31/13
 * Time: 7:49 AM
 * To change this template use File | Settings | File Templates.
 */
public class GpsTrackingService extends Service implements LocationListener
{
    public IBinder onBind(Intent intent)
    {
        return null;
    }

    private final String TAG = "GpsTrackingSvc-Short";

    // Declaring a Location Manager
    private LocationManager locationManager;
    // flag for GPS status
    private boolean isGPSEnabled = false;
    // flag for network status
    private boolean isNetworkEnabled = false;
    // flag for GPS status
    boolean canGetLocation = false;
    // current currentLocation
    private Location currentLocation;
    // cached location
    private Location cachedLocation;

    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 2; // 2 meters

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1000 * 5; // 5s

    private UIThreadHandler uiThreadHandler;
    private GpsDBHandler gpsDBHandler;
    private BusinessDBHandler businessDBHandler;

    public boolean isCanGetLocation()
    {
        isGPSEnabled = locationManager
                .isProviderEnabled(LocationManager.GPS_PROVIDER);
        canGetLocation = isGPSEnabled;
        //--
        return canGetLocation;
    }

    public void InitGpsLocation()
    {
        try
        {
            locationManager = (LocationManager) getApplicationContext().getSystemService(LOCATION_SERVICE);

            // getting GPS status
            isGPSEnabled = locationManager
                    .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (!isGPSEnabled) // && !isNetworkEnabled)
            {
                this.canGetLocation = false;
                return;
            }

            this.canGetLocation = true;
            locationManager.requestLocationUpdates(
                    LocationManager.NETWORK_PROVIDER,
                    MIN_TIME_BW_UPDATES,
                    MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
            //Log.d(TAG, "Location searching...");
            businessDBHandler.addOrUpdateRuntimeValues("GpsStatus", "Available");
        }
        catch (Exception e)
        {
            //Log.e(TAG, e.getMessage());
        }
    }

    @Override
    public void onLocationChanged(Location location)
    {
        if (location != null)
        {
            this.currentLocation = location;
            businessDBHandler.addOrUpdateRuntimeValues("GpsStatus", "OK");
            //Log.d(TAG, "Location changed: " +
            //        this.currentLocation);
            if(uiThreadHandler != null)
            {
                Message msg = new Message();
                msg.what = UIThreadHandler.MSG_GPS_LOCATION_CHANGED;
                Bundle data = new Bundle();
                data.putDouble("Longitude", this.currentLocation.getLongitude());
                data.putDouble("Latitude", this.currentLocation.getLatitude());
                msg.setData(data);
                uiThreadHandler.sendMessage(msg);
            }
        }
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extra)
    {
        String xStatus = "";
        if (status == LocationProvider.AVAILABLE)
            xStatus = "available";
        else if (status == LocationProvider.OUT_OF_SERVICE)
            xStatus = "out of service";
        else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE)
            xStatus = "temporarily unavailable";
        //Log.d(TAG, String.format("[onStatusChanged] %s %s", provider, xStatus));
        businessDBHandler.addOrUpdateRuntimeValues("GpsStatus", xStatus);
    }

    @Override
    public void onProviderEnabled(String provider)
    {
        //Log.d(TAG, String.format("Proviver %s enabled", provider));
        /*WorkingSession.getInstance().ShowToast(getBaseContext(),
                String.format("%s enabled", provider),
                Toast.LENGTH_SHORT);*/
    }

    @Override
    public void onProviderDisabled(String provider)
    {
        //Log.d(TAG, String.format("Proviver %s disabled", provider));
        /*WorkingSession.getInstance().ShowToast(getBaseContext(),
                String.format("%s disabled", provider),
                Toast.LENGTH_SHORT);*/
    }

    public void stopGPS()
    {
        //Log.d(TAG, "stopGPS...");
        if (locationManager != null)
        {
            locationManager.removeUpdates(this);
        }
        businessDBHandler.addOrUpdateRuntimeValues("GpsStatus", "Stop");
    }

    public void resumeGPS()
    {
        //Log.d(TAG, "resumeGPS...");
        if(locationManager != null)
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
    }

    private final class UIThreadHandler extends Handler
    {
        private final String TAG = "GpsUIThreadHandler";
        public static final int MSG_START_SYNC = 0x10;
        public static final int MSG_END_SYNC = 0x11;
        public static final int MSG_ERROR_NO_NETWORK = 0x12;
        public static final int MSG_GPS_LOCATION_CHANGED = 0x13;
        public static final int MSG_GPS_NOT_ENABLED = 0x14;
        public static final int MSG_GPS_STAGE_CHANGED = 0x15;
        public static final int MSG_GPS_LOCATION_SEARCHING = 0x16;

        public UIThreadHandler(Looper lp)
        {
            super(lp);
        }

        @Override
        public void handleMessage(Message msg)
        {
            try
            {
                switch(msg.what)
                {
                    case UIThreadHandler.MSG_START_SYNC:
                    {
                        ShowToast("start data synchronization");
                        break;
                    }
                    case UIThreadHandler.MSG_END_SYNC:
                    {
                        ShowToast("data synchronization finished");
                        break;
                    }
                    case UIThreadHandler.MSG_ERROR_NO_NETWORK:
                    {
                        ShowToast("synchronization error: network not available");
                        break;
                    }
                    case UIThreadHandler.MSG_GPS_LOCATION_CHANGED:
                    {
                        //Log.d(TAG, "MSG_GPS_LOCATION_CHANGED...");
                        /*Bundle data = msg.getData();
                        ShowToast("location changed: \n"
                                + data.getDouble("Longitude")
                                + ", "
                                + data.getDouble("Latitude"));*/
                        break;
                    }
                    case UIThreadHandler.MSG_GPS_NOT_ENABLED:
                    {
                        ShowToast("GPS not enabled");
                        break;
                    }
                    case UIThreadHandler.MSG_GPS_STAGE_CHANGED:
                    {
                        Bundle data = msg.getData();
                        //Log.d(TAG, "Current location is " +
                        //        data.getString("NEW_STAGE_NAME", ""));
                        ShowToast("Current location is " +
                            data.getString("NEW_STAGE_NAME", ""));
                        break;
                    }
                    case UIThreadHandler.MSG_GPS_LOCATION_SEARCHING:
                    {
                        ShowToast("Location searching...");
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                //Log.e(TAG, ex.getMessage());
            }
        }

        private Toast currentToast;
        private void ShowToast(String text)
        {
            if(isMyFriendActive())
            {
                currentToast = Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT);
                currentToast.show();
                //---
                Handler handler = new Handler();
                handler.postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        currentToast.cancel();
                    }
                },
                700); //700 milliseconds
            }
        }
    }

    private boolean isMyFriendActive()
    {
        ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        // get the info from the currently running task
        List< ActivityManager.RunningTaskInfo > taskInfo = am.getRunningTasks(1);

        //Log.d(TAG, "CURRENT Activity :: "
        //        + taskInfo.get(0).topActivity.getClassName());
        String activeName = taskInfo.get(0).topActivity.getClassName();
        if(activeName.contains("valuecomusa.fijibus"))
            return true;
        return false;
    }

    private Timer scheduler;
    private final int SCHEDULE_INTERVAL = 10*1000; //10s

    @Override
    public void onCreate()
    {
        //Log.d(TAG, "onCreate...");
        HandlerThread uiThread = new HandlerThread("UIThreadHandler");//to prepare a looper
        uiThread.start();
        uiThreadHandler = new UIThreadHandler(uiThread.getLooper()); //to process UI such as AlertDialog,...
        //---
        gpsDBHandler = new GpsDBHandler(getApplicationContext());
        businessDBHandler = new BusinessDBHandler(getApplicationContext());
        scheduler = new Timer();
        //---
        InitGpsLocation();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        //Log.d(TAG, "[onStartCommand] Starting...");
        scheduler.schedule(
                timerTask,
                SCHEDULE_INTERVAL, //first run after 10s
                SCHEDULE_INTERVAL); //next run each 10s after first run

        return START_STICKY;
    }

    @Override
    public void onDestroy()
    {
        //Log.d(TAG, "Stopping...");
        stopGPS();
        //--
        if(scheduler != null)
            scheduler.cancel();
    }

    private  boolean isTaskRunning = false;
    private TimerTask timerTask = new TimerTask()
    {
        private final String TAG = "GpsTrackingTask";
        @Override
        public void run()
        {
            if(isTaskRunning)
                return;
            isTaskRunning = true;
            //Log.d(TAG, "------- let's go--------");
            try
            {
                doScheduleTask();
            }
            catch (Exception ex)
            {
                //Log.e(TAG, "EXCEPTION at run(). " + ex.getMessage());
            }

            isTaskRunning = false;
        }
    };

    private void doScheduleTask()
    {
        if(!isCanGetLocation() && uiThreadHandler != null)
        {
            uiThreadHandler.sendEmptyMessage(UIThreadHandler.MSG_GPS_NOT_ENABLED);
        }
        //--
        if(currentLocation == null)
        {
            //Log.d(TAG, "Location Service does not ready.");
            return;
        }
        //--
        //--
        //Log.d(TAG, "Cached location: " + cachedLocation);
        //Log.d(TAG, "New location: " + currentLocation);
        if(cachedLocation != null)
            //Log.d(TAG, "Distance: " + cachedLocation.distanceTo(currentLocation));
        cachedLocation = currentLocation;
        //--
        JournalValue journal = businessDBHandler.getNewestJournal();
        if(journal == null)
        {
            //Log.d(TAG, "Could not get newest journal.");
            return;
        }
        //Log.d(TAG, "Newest journal: " + journal.ISN);
        //--update journal's location
        boolean needUpdateStationName = false;
        if(journal.Longitude == 0 && journal.Latitude == 0) //not set yet
        {
            journal.Longitude = cachedLocation.getLongitude();
            journal.Latitude = cachedLocation.getLatitude();

            long rs = businessDBHandler.updateJournal(journal);
            //Log.d(TAG, String.format("Update location of journal: %d, result: %d",
            //        journal.ISN, rs));
            needUpdateStationName = true;
        }
        //--reload current location from db
        //Log.d(TAG, "Reload current location of journal from DB...");
        GpsCurrentLocationValue xx = gpsDBHandler.getCurrentLocation(journal.ISN);
        if(xx == null) //not found
        {
            //Log.d(TAG, "Could not found location of journal from DB");
            xx = new GpsCurrentLocationValue();
        }
        //--update current location
        //Log.d(TAG, "Update current location to DB...");
        xx.JournalISN = journal.ISN;
        xx.Accuracy = cachedLocation.getAccuracy();
        xx.Altitude = cachedLocation.getAltitude();
        xx.Bearing = cachedLocation.getBearing();
        xx.Latitude = cachedLocation.getLatitude();
        xx.Longitude = cachedLocation.getLongitude();
        xx.Speed = cachedLocation.getSpeed();

        long rs = gpsDBHandler.addOrUpdateCurrentLocation(xx);
        //Log.d(TAG, "Update result: " + rs);

        /*if(cachedLocation != null && cachedLocation.distanceTo(currentLocation) == 0)
        {
            //Log.d(TAG, "[doScheduleTask] Nothing change.");
            return;
        }*/

        //--get stages of route
        //Log.d(TAG, "Get stages of route: " + journal.RouteISN);
        List<StagesOfRouteValue> stages = businessDBHandler.getStagesOfRoute(journal.RouteISN);
        if(stages == null || stages.size() == 0)
        {
            //Log.d(TAG, "Could not get stages of route " + journal.RouteISN);
            return;
        }
        //---find closest stage
        //Log.d(TAG, "Find closest stage...");
        int closestStage = 0;
        int closestBusStation = 0;
        String closestStageName = "";
        float minDistance = Float.MAX_VALUE;

        for(StagesOfRouteValue sor: stages)
        {
            /*The computed distance is stored in results[0].
            If results has length 2 or greater, the initial bearing is stored in results[1].
            If results has length 3 or greater, the final bearing is stored in results[2].*/
            float[] compareResult = new float[1];
            try
            {
                Location.distanceBetween(
                        cachedLocation.getLatitude(),
                        cachedLocation.getLongitude(),
                        sor.Latitude,
                        sor.Longitude,
                        compareResult
                );
                //Log.d(TAG, String.format("[doScheduleTask] Distance of (%s, %s) with (%s, %s) is %s",
//                        String.valueOf(cachedLocation.getLatitude()),
//                        String.valueOf(cachedLocation.getLongitude()),
//                        String.valueOf(sor.Latitude),
//                        String.valueOf(sor.Longitude),
//                        String.valueOf(compareResult[0]))
//                );
                if(compareResult[0] < minDistance )
                {
                    minDistance = compareResult[0];
                    closestStage = sor.StageISN;
                    closestStageName = sor.StationName;
                    closestBusStation = sor.BusStationISN;
                    //Log.e(TAG, "Min distance: " + minDistance
//                            + ", stageISN: " + closestStage
//                            + ", busStationISN: " + sor.BusStationISN);
                }
            }
            catch (Exception ex)
            {
                //Log.e(TAG, "[doScheduleTask] Calculate distance error. " + ex.getMessage());
            }
        }
        //Log.d(TAG, "Closest stage: " + closestStageName);
        if(closestStage != xx.ClosestStage ||
                closestBusStation != xx.ClosestBusStation) //need to update and notify
        {
            xx.ClosestStage = closestStage;
            xx.ClosestBusStation = closestBusStation;
            //Log.d(TAG, "Update current location with new closest Stage and Bus Station");
            rs = gpsDBHandler.addOrUpdateCurrentLocation(xx);
            //Log.d(TAG, "Update result: " + rs);
            //--send notification
            if(uiThreadHandler != null)
            {
                Message msg = new Message();
                msg.what = UIThreadHandler.MSG_GPS_STAGE_CHANGED;
                Bundle data = new Bundle();
                data.putString("NEW_STAGE_NAME", closestStageName);
                msg.setData(data);
                //--
                uiThreadHandler.sendMessage(msg);
            }
        }

        if(needUpdateStationName)
        {
            journal.ClosestBusStation = closestStageName;

            rs = businessDBHandler.updateJournal(journal);
            //Log.d(TAG, String.format("Update ClosestBusStation of journal: %d, result: %d",
//                    journal.ISN, rs));
        }
        /*if(uiThreadHandler != null)
        {
            Message msg = new Message();
            msg.what = UIThreadHandler.MSG_GPS_STAGE_CHANGED;
            Bundle data = new Bundle();
            data.putString("NEW_STAGE_NAME", closestStageName);
            msg.setData(data);
            //--
            uiThreadHandler.sendMessage(msg);
        }*/
    }
}
